package service

import (
	"encoding/json"
	"errors"
	"fmt"
	"gin-luban-server/global"
	"gin-luban-server/model"
	"github.com/pkg/sftp"
	gossh "golang.org/x/crypto/ssh"
	"io/ioutil"
	"mime/multipart"
	"net"
	"os"
	"path"
	"strconv"
	"time"
)

type SftpClientInfo struct {
	Username  string `json:"username"`
	SshUser   string `json:"ssh_user"`
	Password  string `json:"password"`
	IpAddress string `json:"ipaddress"`
	Port      int    `json:"port"`
	sshType   string `json:"sshType"`
	sshKey    string `json:"sshKey"`
	isAdmin   bool
	isProxy   bool
	ProxySshType string  `json:"proxy_sshType"`
	ProxyUser    string  `json:"proxy_user"`
	ProxySshKey  string  `json:"proxy_sshKey"`
	ProxyPassword string `json:"proxy_password"`
	ProxyHost     string `json:"proxy_host"`
	ProxyPort     int    `json:"proxy_port"`
}

const maxPacket = 1024 * 32 * 16

//@author: heyibo
//@function: GetSftpClientInfo
//@description: 获取client客户端信息
//@return: err error, clientInfo SftpClientInfo
func GetSftpClientInfo(id float64,uuid string) (clientInfo SftpClientInfo, err error){
	var jumps model.JumpServerUser
	err = global.GVA_DB.Model(&model.JumpServerUser{}).Where("id = ?",id).First(&jumps).Error
	if err !=nil {
		return clientInfo, errors.New("查询服务器失败")
	}
	port,_ :=strconv.Atoi(jumps.SshPort)
	clientInfo.Username =  jumps.UserName
	clientInfo.SshUser = jumps.SshUser
	clientInfo.IpAddress =jumps.SshIpAddress
	clientInfo.Port = port
	clientInfo.Password = jumps.SshPassword
	clientInfo.sshType = jumps.SshType
	clientInfo.sshKey = jumps.SshKey
	clientInfo.isAdmin = CheckUserToken(uuid)
	clientInfo.ProxyUser = jumps.ProxyUser
	clientInfo.ProxySshType = jumps.ProxySshType
	clientInfo.ProxyPassword = jumps.ProxyPassword
	clientInfo.ProxyHost = jumps.ProxyHost
	clientInfo.ProxySshKey = jumps.ProxySshKey
	proxyPort,_ :=strconv.Atoi(jumps.ProxyPort)
	clientInfo.ProxyPort = proxyPort
	if jumps.XShellProxy == "connect" {
		clientInfo.isProxy = false
	}else {
		clientInfo.isProxy = true
	}
	return
}
//@author: heyibo
//@function: NewSshClient
//@description: 新建ssh客户端
//@return: err error, client *gossh.Client
func NewSshClient(clientInfo SftpClientInfo)(client *gossh.Client,err error)  {
	if clientInfo.isProxy {
		proxyConfig,err :=NewSshClientConfig(clientInfo.ProxyUser,clientInfo.ProxyPassword,clientInfo.ProxySshType,clientInfo.ProxySshKey)
		if err !=nil {
			return client,errors.New("代理配置文件出错！")
		}
		proxyAddr := fmt.Sprintf("%s:%d", clientInfo.ProxyHost, clientInfo.ProxyPort)
		targetConfig, err :=NewSshClientConfig(clientInfo.SshUser,clientInfo.Password,clientInfo.sshType,clientInfo.sshKey)
		targetAddr :=fmt.Sprintf("%s:%d", clientInfo.IpAddress, clientInfo.Port)
		client,err :=NewSshProxyClient(targetConfig,proxyConfig,targetAddr,proxyAddr)
		if err !=nil {
			return client,err
		}
		return client,err
	}else {
		config := gossh.Config{
			Ciphers: []string{"aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-gcm@openssh.com", "arcfour256", "arcfour128", "aes128-cbc", "3des-cbc", "aes192-cbc", "aes256-cbc"},
		}
		clientConfig := &gossh.ClientConfig{
			User:    clientInfo.SshUser,
			Timeout: 300 * time.Second,
			Config:  config,
			HostKeyCallback: func(hostname string, remote net.Addr, key gossh.PublicKey) error {
				return nil
			},
		}
		switch clientInfo.sshType {
		case "password":
			clientConfig.Auth = []gossh.AuthMethod{gossh.Password(clientInfo.Password)}
		case "key":
			signer, _ := gossh.ParsePrivateKey([]byte(clientInfo.sshKey))
			clientConfig.Auth = []gossh.AuthMethod{gossh.PublicKeys(signer)}
		default:
			return nil, fmt.Errorf("unknow ssh auth type: %s", clientInfo.sshType)
		}
		addr := fmt.Sprintf("%s:%d", clientInfo.IpAddress, clientInfo.Port)
		if client, err = gossh.Dial("tcp", addr, clientConfig); err != nil {
			return client, err
		}
		return client,err
	}
}

//@author: heyibo
//@function: NewSftpClient
//@description: 新建sftp客户端
//@return: err error, sftpClient *sftp.Client

func NewSftpClient(sshClient *gossh.Client) (sftpClient *sftp.Client,err error) {
	sftpClient, err = sftp.NewClient(sshClient)
	if err != nil {
		return nil, err
	}
	//defer sshClient.Close()
	return
}

//@author: heyibo
//@function: GetSftpLs
//@description: 获取目录文件
//@return: err error, fileList []model.SftpLs

func GetSftpLs(id float64,uuid string,dirPath string)(fileList []model.SftpLs,err error)  {
	if dirPath == "" {
		dirPath = "/"
	}
	clientInfo,err := GetSftpClientInfo(id,uuid)
	if err != nil {
		return nil,err
	}
	if clientInfo.isAdmin {
		sshClient,err :=NewSshClient(clientInfo)
		if err != nil {
			return nil,err
		}
		sftpClient,err :=NewSftpClient(sshClient)
		if err != nil {
			return nil,err
		}
		files, err := sftpClient.ReadDir(dirPath)
		if err != nil {
			return nil,err
		}
		for _, file := range files {
				tt := model.SftpLs{Name: file.Name(), Size: FormatFileSize(file.Size()), Path: path.Join(dirPath, file.Name()), Time: file.ModTime(), Mod: file.Mode().String(), IsDir: file.IsDir()}
				fileList = append(fileList, tt)
		}
		return fileList,err
		defer  sshClient.Close()
		defer sftpClient.Close()
	}else {
		err = errors.New("用户不存在！")
	}
	return fileList,err
}

//@author: heyibo
//@function: GetSftpLs
//@description: 获取目录文件
//@return: err error, fileList []model.SftpLs
func DeleteSftpFile(id float64,uuid string,filePath string) (err error) {
	clientInfo,_ := GetSftpClientInfo(id,uuid)
	sshClient,_ :=NewSshClient(clientInfo)
	sftpClient,_ :=NewSftpClient(sshClient)
    fileStat,_ :=sftpClient.Stat(filePath)
    if fileStat.IsDir() {
    	return errors.New("路径为文件目录！")
	}
	err = sftpClient.Remove(filePath)
	if err != nil {
       return errors.New("删除文件出错！")
	}
	jumpLogs :=model.JumpServerSshLogs{SshUser: clientInfo.SshUser,UserName: clientInfo.Username,ClientIp: clientInfo.IpAddress,SshPort:strconv.Itoa(clientInfo.Port),Remark: "sftp日志信息"}
	jumpLogs.Status = 16
	jumpLogs.StartedAt =time.Now()
	sftpLog :=[]string{fmt.Sprintf("%s  %s", "rm -rf", filePath)}
	sftpLogs, _ := json.Marshal(sftpLog)
	jumpLogs.WebClientLogs = string(sftpLogs)
	err =CreateJumpServerSshLogs(jumpLogs)
	defer sftpClient.Close()
	return 
}

//@author: heyibo
//@function: UpLoadFileSftp
//@description: 上传文件到目录
//@return: err error
func UpLoadFileSftp(id float64,uuid string,desDir string,header *multipart.FileHeader)(err error) {
	clientInfo,err := GetSftpClientInfo(id,uuid)
	if err != nil {
		return err
	}
	sshClient,err :=NewSshClient(clientInfo)
	if err != nil {
		return err
	}
	sftpClient,err :=NewSftpClient(sshClient)
	if err != nil {
		return err
	}
	srcFile, err := header.Open()
	if err != nil {
		return err
	}
	dstFile, err := sftpClient.Create(path.Join(desDir, header.Filename))
	if err != nil {
		return err
	}
	defer srcFile.Close()
	defer dstFile.Close()
	_, err = dstFile.ReadFrom(srcFile)
	if err != nil {
		return errors.New("文件上传失败!")
	}
	return
}
//@author: heyibo
//@function: FormatFileSize
//@description: 字节的单位转换 保留两位小数
//@return: err error

func FormatFileSize(fileSize int64) (size string) {
	if fileSize < 1024 {
		//return strconv.FormatInt(fileSize, 10) + "B"
		return fmt.Sprintf("%.2fB", float64(fileSize)/float64(1))
	} else if fileSize < (1024 * 1024) {
		return fmt.Sprintf("%.2fKB", float64(fileSize)/float64(1024))
	} else if fileSize < (1024 * 1024 * 1024) {
		return fmt.Sprintf("%.2fMB", float64(fileSize)/float64(1024*1024))
	} else if fileSize < (1024 * 1024 * 1024 * 1024) {
		return fmt.Sprintf("%.2fGB", float64(fileSize)/float64(1024*1024*1024))
	} else if fileSize < (1024 * 1024 * 1024 * 1024 * 1024) {
		return fmt.Sprintf("%.2fTB", float64(fileSize)/float64(1024*1024*1024*1024))
	} else { //if fileSize < (1024 * 1024 * 1024 * 1024 * 1024 * 1024)
		return fmt.Sprintf("%.2fEB", float64(fileSize)/float64(1024*1024*1024*1024*1024))
	}
}

//@author: heyibo
//@function: NewSshClientConfig
//@description: 生成ssh配置文件
//@return: err error
func NewSshClientConfig(sshUser,sshPassword,sshType,sshKey  string) (clientConfig *gossh.ClientConfig, err error) {
	if sshUser == "" {
		return nil, errors.New("sshUser can not be empty")
	}
	config := gossh.Config{
		Ciphers: []string{"aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-gcm@openssh.com", "arcfour256", "arcfour128", "aes128-cbc", "3des-cbc", "aes192-cbc", "aes256-cbc"},
	}
	clientConfig = &gossh.ClientConfig{
		User:    sshUser,
		Timeout: 300 * time.Second,
		Config:  config,
		HostKeyCallback: func(hostname string, remote net.Addr, key gossh.PublicKey) error {
			return nil
		},
	}
	switch sshType {
	case "password":
		clientConfig.Auth = []gossh.AuthMethod{gossh.Password(sshPassword)}
	case "key":
		signer, _ := gossh.ParsePrivateKey([]byte(sshKey))
		clientConfig.Auth = []gossh.AuthMethod{gossh.PublicKeys(signer)}
	default:
		return nil, fmt.Errorf("unknow ssh auth type: %s", sshType)
	}
	return clientConfig,err
}
//@author: heyibo
//@function: 生成代理client
//@description: 字节的单位转换 保留两位小数
//@return: err error
func NewSshProxyClient(targetSshConfig, proxySshConfig *gossh.ClientConfig, targetAddr, proxyAddr string) (client *gossh.Client, err error) {
	proxyClient, err := gossh.Dial("tcp", proxyAddr, proxySshConfig)
	if err != nil {
		return
	}
	conn, err := proxyClient.Dial("tcp", targetAddr)
	if err != nil {
		return
	}
	ncc, chans, reqs, err := gossh.NewClientConn(conn, targetAddr, targetSshConfig)
	if err != nil {
		return
	}
	client = gossh.NewClient(ncc, chans, reqs)
	return
}

//@author: heyibo
//@function: UploadFile
//@description: 向远程上传文件
//@return: err error
func UploadFile(sftpClient *sftp.Client, localFilePath string, remotePath string) (err error){
	srcFile, err := os.Open(localFilePath)
	if err != nil {
        return err
	}
	defer srcFile.Close()
	var remoteFileName = path.Base(localFilePath)

	dstFile, err := sftpClient.Create(path.Join(remotePath, remoteFileName))
	if err != nil {
       return err
	}
	defer dstFile.Close()

	ff, err := ioutil.ReadAll(srcFile)
	if err != nil {
		return err
	}
	dstFile.Write(ff)
	defer sftpClient.Close()
    return
}